// SPDX-License-Identifier: GPL-2.0
/*
 * GPU: VRAM mapping on BAR
 *
 * BuddyZhang1 <buddy.zhang@aliyun.com>
 * BiscuitOS <http://biscuitos.cn/blog/BiscuitOS_Catalogue/>
 *
 * Copyright (c) 2012-2015 Jiri Slaby
 */
#include "qemu/osdep.h"
#include "qemu/units.h"
#include "hw/pci/pci.h"
#include "hw/pci/msix.h"
#include "qemu/timer.h"
#include "qemu/main-loop.h" /* iothread mutex */
#include "qapi/visitor.h"

/* BiscuitOS PCI QOM */
#define TYPE_PCI_BISCUITOS_DEVICE 	"BiscuitOS-GPU-VRAM-BAR"
#define BISCUITOS(obj)  		\
	OBJECT_CHECK(BiscuitOS_PCI_State, obj, TYPE_PCI_BISCUITOS_DEVICE)

/* PCI VENDOR:DEVICE ID */
#define BISCUITOS_PCI_VENDOR_ID		0x1010
#define BISCUITOS_PCI_DEVICE_ID		0x1991
/* PCI BAR Layout */
#define BAR_MMIO			0x00
#define BAR_SIZE			0x200000

typedef struct {
	PCIDevice	pdev;
	MemoryRegion	mmio;	/* VRAM */

	/* GPU VRAM */
	char		*vram;
} BiscuitOS_PCI_State;

static void BiscuitOS_bar_write(void *opaque, hwaddr addr, 
					uint64_t val, unsigned size)
{
	BiscuitOS_PCI_State *bps = BISCUITOS(opaque);
	char *vram = bps->vram + addr;

	memcpy(vram, &val, size);
}

static uint64_t BiscuitOS_bar_read(void *opaque, hwaddr addr, unsigned size)
{
	BiscuitOS_PCI_State *bps = BISCUITOS(opaque);
	char *vram = bps->vram + addr;
	uint64_t value = ~0ULL;

	memcpy(&value, vram, size);

	return value;
}

/* MMIO MR OPS */
static const MemoryRegionOps BiscuitOS_mmio_ops = {
	.write      = BiscuitOS_bar_write,
	.read	    = BiscuitOS_bar_read,
	.endianness = DEVICE_NATIVE_ENDIAN,
	.impl = {
		.min_access_size = 4, /* MOV min 32bit */
		.max_access_size = 4, /* MOV max 32bit */
	},
};

/* GPU EMULATE */
static void BiscuitOS_pci_realize(PCIDevice *pdev, Error **errp)
{
	BiscuitOS_PCI_State *bps = BISCUITOS(pdev);

	/* Register MMIO BAR MR */
	memory_region_init_io(&bps->mmio, OBJECT(bps), &BiscuitOS_mmio_ops,
				bps, "BiscuitOS-GPU-VRAM-BAR", BAR_SIZE);
	/* Register MMIO BAR */
	pci_register_bar(pdev, BAR_MMIO,
				PCI_BASE_ADDRESS_SPACE_MEMORY, &bps->mmio);

	/* GPU VRAM: Anonymous Memory */
	bps->vram = mmap(NULL, BAR_SIZE,
			 PROT_READ | PROT_WRITE,
			 MAP_ANONYMOUS | MAP_PRIVATE,
			 -1,
			 0);
	if (bps->vram == MAP_FAILED) {
		qemu_log("GPU VRAM ALLOC Failed.");
		return;
	}
}

/* RECLAIM */
static void BiscuitOS_pci_uninit(PCIDevice *pdev)
{
	BiscuitOS_PCI_State *bps = BISCUITOS(pdev);

	munmap(bps->vram, BAR_SIZE);
}

static void BiscuitOS_class_init(ObjectClass *class, void *data)
{
	DeviceClass *dc = DEVICE_CLASS(class);
	PCIDeviceClass *k = PCI_DEVICE_CLASS(class);

	k->realize   = BiscuitOS_pci_realize;
	k->exit      = BiscuitOS_pci_uninit;
	k->vendor_id = BISCUITOS_PCI_VENDOR_ID;
	k->device_id = BISCUITOS_PCI_DEVICE_ID;
	k->revision  = 0x10;
	k->class_id  = PCI_CLASS_OTHERS;
	set_bit(DEVICE_CATEGORY_MISC, dc->categories);
}

static void BiscuitOS_pci_register_types(void)
{
	static InterfaceInfo interfaces[] = {
		{ INTERFACE_CONVENTIONAL_PCI_DEVICE },
		{ },
	};
	static const TypeInfo BiscuitOS_pci_info = {
		.name          = TYPE_PCI_BISCUITOS_DEVICE,
		.parent        = TYPE_PCI_DEVICE,
		.instance_size = sizeof(BiscuitOS_PCI_State),
		.class_init    = BiscuitOS_class_init,
		.interfaces = interfaces,
	};

	type_register_static(&BiscuitOS_pci_info);
}
type_init(BiscuitOS_pci_register_types)
